/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.captcha.support;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vacoor.nothing.web.captcha.ImageCaptcha;
import org.vacoor.nothing.web.captcha.ImageCaptchaValidationScheduler;
import org.vacoor.nothing.web.captcha.ValidatingImageCaptchaService;

import java.util.Collection;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author vacoor
 */
public abstract class GeneralValidatingCaptchaService extends GeneralImageCaptchaService implements ValidatingImageCaptchaService {
    private static final Logger LOG = LoggerFactory.getLogger(GeneralValidatingCaptchaService.class);

    private static final long DEFAULT_TIMEOUT = TimeUnit.MINUTES.toMillis(10);
    private static final long DEFAULT_VALIDATION_INTERVAL = TimeUnit.HOURS.toMillis(1);

    private long timeout;  // 验证码超时时间
    private boolean validationSchedulerEnabled;  // 验证码有效性验证计划是否启用
    private ImageCaptchaValidationScheduler imageCaptchaValidationScheduler;  // 验证码有效性验证计划
    private long validationInterval;   // 验证间隔

    public GeneralValidatingCaptchaService() {
        timeout = DEFAULT_TIMEOUT;
        validationSchedulerEnabled = true;
        validationInterval = DEFAULT_VALIDATION_INTERVAL;
    }

    @Override
    protected ImageCaptcha generateAndStoreCaptcha(String id) {
        enableCaptchaValidationIfNecessary();
        return super.generateAndStoreCaptcha(id);
    }

    @Override
    public boolean doValidate(ImageCaptcha captcha, String response) {
        return !isExpired(captcha) && super.doValidate(captcha, response);
    }

    /**
     * 验证码是否过期
     *
     * @param captcha
     * @return
     */
    protected boolean isExpired(ImageCaptcha captcha) {
        return null == captcha || (captcha.hasGetChallengeBeenCalled() && timeout < new Date().getTime() - captcha.getEffectiveDate().getTime());
    }

    /**
     * 创建验证码有效性验证计划
     *
     * @return
     */
    protected ImageCaptchaValidationScheduler createCaptchaValidationScheduler() {
        SimpleImageCaptchaValidationScheduler scheduler;

        if (LOG.isDebugEnabled()) {
            LOG.debug("No captchaValidationScheduler set.  Attempting to create default instance.");
        }
        scheduler = new SimpleImageCaptchaValidationScheduler(this);
        scheduler.setInterval(getValidationInterval());
        if (LOG.isTraceEnabled()) {
            LOG.trace("Created default CaptchaValidationScheduler instance of type [" + scheduler.getClass().getName() + "].");
        }
        return scheduler;
    }

    private void enableCaptchaValidationIfNecessary() {
        ImageCaptchaValidationScheduler scheduler = getImageCaptchaValidationScheduler();
        if (isValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
            enableCaptchaValidation();
        }
    }

    /**
     * 启用验证码有效性验证
     */
    protected void enableCaptchaValidation() {
        ImageCaptchaValidationScheduler scheduler = getImageCaptchaValidationScheduler();
        if (scheduler == null) {
            scheduler = createCaptchaValidationScheduler();
            setImageCaptchaValidationScheduler(scheduler);
        }
        if (LOG.isInfoEnabled()) {
            LOG.info("Enabling captcha validation scheduler...");
        }
        scheduler.enableCaptchaValidation();
        afterCaptchaValidationEnabled();
    }

    protected void afterCaptchaValidationEnabled() {
    }

    /**
     * 禁用验证码有效性验证
     */
    protected void disableCaptchaValidation() {
        beforeCaptchaValidationDisabled();
        ImageCaptchaValidationScheduler scheduler = getImageCaptchaValidationScheduler();
        if (scheduler != null) {
            try {
                scheduler.disableCaptchaValidation();
                if (LOG.isInfoEnabled()) {
                    LOG.info("Disabled captcha validation scheduler.");
                }
            } catch (Exception e) {
                if (LOG.isDebugEnabled()) {
                    String msg = "Unable to disable CaptchaValidationScheduler.  Ignoring (shutting down)...";
                    LOG.debug(msg, e);
                }
            }
            setImageCaptchaValidationScheduler(null);
        }
    }

    protected void beforeCaptchaValidationDisabled() {
    }

    @Override
    public void validateCaptchas() {
        if (LOG.isInfoEnabled()) {
            LOG.info("Validating all captchas...");
        }

        int invalidCount = 0;

        Collection<ImageCaptcha> activeSessions = getActiveCaptchas();

        if (activeSessions != null && !activeSessions.isEmpty()) {
            for (ImageCaptcha s : activeSessions) {
                if (!isExpired(s)) {
                    continue;
                }

                imageCaptchaDao.delete(s.getId());

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Invalidated captcha with id [" + s.getId() + "] expired");
                }
                invalidCount++;
            }
        }

        if (LOG.isInfoEnabled()) {
            String msg = "Finished captcha validation.";
            if (invalidCount > 0) {
                msg += "  [" + invalidCount + "] captcha were stopped.";
            } else {
                msg += "  No captcha were stopped.";
            }
            LOG.info(msg);
        }
    }

    protected Collection<ImageCaptcha> getActiveCaptchas() {
        return getImageCaptchaDao().getActiveCaptchas();
    }

    /* *******************
     *   getter / setter
     * ***************** */
    public boolean isValidationSchedulerEnabled() {
        return validationSchedulerEnabled;
    }

    public void setValidationSchedulerEnabled(boolean validationSchedulerEnabled) {
        this.validationSchedulerEnabled = validationSchedulerEnabled;
    }

    public ImageCaptchaValidationScheduler getImageCaptchaValidationScheduler() {
        return imageCaptchaValidationScheduler;
    }

    public void setImageCaptchaValidationScheduler(ImageCaptchaValidationScheduler imageCaptchaValidationScheduler) {
        this.imageCaptchaValidationScheduler = imageCaptchaValidationScheduler;
    }

    public long getValidationInterval() {
        return validationInterval;
    }

    public void setValidationInterval(long validationInterval) {
        this.validationInterval = validationInterval;
    }
}
